# vjoy_control.py
'''
    vJoy虚拟手柄控制DCS World - 模块级函数版本
    功能：通过vJoy虚拟手柄模拟飞行控制输入
    调用方式：from vjoy_control import *
'''
import logging
import time
import traceback

import pygetwindow as gw
import pyvjoy

# --------------------------
# 全局变量和常量定义
# --------------------------

# vJoy设备实例
_vjoy_device = pyvjoy.VJoyDevice(1)

# 轴参数定义 (vJoy轴范围：0x0000-0x8000)
AXIS_NEUTRAL = 0x4000  # 轴中立位置
AXIS_MAX = 0x8000  # 轴最大值
AXIS_MIN = 0x0000  # 轴最小值

# 油门当前位置 (0.0-1.0)
current_throttle = 0.5

# --------------------------
# 按钮定义（对应vJoy设备按钮编号）
# --------------------------
BUTTON_FLAPS = 1  # 襟翼打开/关闭按钮（主）
BUTTON_FLAPS_LAND = 2  # 着陆襟翼按钮
BUTTON_AIRBRAKE_EXTEND = 3  # 扰流板/减速板 打开
BUTTON_AIRBRAKE_RETRACT = 10  # 扰流板/减速板 关闭
BUTTON_GEAR_UP = 4  # 起落架收起按钮
BUTTON_GEAR_DOWN = 5  # 起落架放下按钮
BUTTON_WHEEL_BRAKE = 6  # 机轮刹车按钮
BUTTON_REVIVE = 7  # 飞机复活/重置按钮 在通用里设置
BUTTON_RUDDER_LEFT = 8  # 脚舵左微调按钮（备用）
BUTTON_RUDDER_RIGHT = 9  # 脚舵右微调按钮（备用）


# --------------------------
# 内部工具函数
# --------------------------

# 全局轴状态跟踪
_axis_states = {
    'x': AXIS_NEUTRAL,  # 滚转
    'y': AXIS_NEUTRAL,  # 俯仰
    'z': AXIS_NEUTRAL,  # 油门
    'rz': AXIS_NEUTRAL  # 脚舵
}

# 在全局变量部分添加
_last_rudder_value = 0.0  # 记录脚舵最后设置值（-1.0到1.0范围）

def _init_all_axes():
    """初始化所有轴到中立位置"""
    axes = {
        pyvjoy.HID_USAGE_X: AXIS_NEUTRAL,
        pyvjoy.HID_USAGE_Y: AXIS_NEUTRAL,
        pyvjoy.HID_USAGE_Z: int(0.5 * AXIS_MAX),  # 油门中立
        pyvjoy.HID_USAGE_RZ: AXIS_NEUTRAL
    }
    for axis, value in axes.items():
        _vjoy_device.set_axis(axis, value)
        _axis_states['x' if axis==pyvjoy.HID_USAGE_X else
                    'y' if axis==pyvjoy.HID_USAGE_Y else
                    'z' if axis==pyvjoy.HID_USAGE_Z else 'rz'] = value


def _set_axis(axis, value):
    """改进的轴设置函数，减少干扰"""
    # 只更新目标轴，不触碰其他轴
    if axis == pyvjoy.HID_USAGE_X:
        _vjoy_device.set_axis(pyvjoy.HID_USAGE_X,
                              int(AXIS_NEUTRAL + value * (AXIS_MAX - AXIS_NEUTRAL)))
    elif axis == pyvjoy.HID_USAGE_Y:
        _vjoy_device.set_axis(pyvjoy.HID_USAGE_Y,
                              int(AXIS_NEUTRAL + value * (AXIS_MAX - AXIS_NEUTRAL)))
    elif axis == pyvjoy.HID_USAGE_Z:
        _vjoy_device.set_axis(pyvjoy.HID_USAGE_Z, int(value * AXIS_MAX))
    elif axis == pyvjoy.HID_USAGE_RZ:
        _vjoy_device.set_axis(pyvjoy.HID_USAGE_RZ,
                              int(AXIS_NEUTRAL + value * (AXIS_MAX - AXIS_NEUTRAL)))
    # 添加延迟减少干扰
    #time.sleep(0.02)  # 20ms延迟



def _sync_all_axes():
    """同步所有轴状态到vJoy设备"""
    _vjoy_device.set_axis(pyvjoy.HID_USAGE_X, _axis_states['x'])
    _vjoy_device.set_axis(pyvjoy.HID_USAGE_Y, _axis_states['y'])
    _vjoy_device.set_axis(pyvjoy.HID_USAGE_Z, _axis_states['z'])
    _vjoy_device.set_axis(pyvjoy.HID_USAGE_RZ, _axis_states['rz'])

def _press_button(button_id):
    """按下并释放按钮的内部函数"""
    _vjoy_device.set_button(button_id, 1)  # 按下按钮
    time.sleep(0.1)
    _vjoy_device.set_button(button_id, 0)  # 释放按钮


# --------------------------
# 窗口控制函数
# --------------------------

def activate_dcs_window():
    """激活DCS游戏窗口"""
    dcs_window_title = "Digital Combat Simulator"
    # dcs_window_title = "vjoy Monitor"
    windows = gw.getWindowsWithTitle(dcs_window_title)
    if not windows:
        print("[错误] 未找到DCS窗口！请确保游戏已启动")
        raise Exception(f"未找到窗口: {dcs_window_title}")

    dcs_window = windows[0]
    for _ in range(3):  # 最多尝试3次
        try:
            dcs_window.activate()
            time.sleep(2)  # 等待窗口激活
            if dcs_window.isActive:
                print("[状态] DCS窗口已激活")
                return True
        except Exception as e:
            print(f"[警告] 窗口激活失败: {e}")

    print("[错误] 无法激活DCS窗口")
    raise Exception("窗口激活失败")


# --------------------------
# 基础轴控制函数
# --------------------------

def set_pitch(value):
    """设置俯仰轴 (-1.0=拉杆/机头向上, 1.0=推杆/机头向下)
    (注意：DCS中实际方向为：1.0=拉杆/机头向上, -1.0=推杆/机头向下，与常规定义相反)
    """
    #_axis_states['y'] = int(AXIS_NEUTRAL + value * (AXIS_MAX - AXIS_NEUTRAL))
    _set_axis(pyvjoy.HID_USAGE_Y, value)
    #_sync_all_axes()  # 同步所有轴状态
    if value == 0:
        print("[俯仰控制反馈] 俯仰回中")
    else:
        print(f"[俯仰控制反馈] 俯仰 {'推杆' if value < 0 else '拉杆'} {abs(value) * 100:.0f}%")


def set_roll(value):
    """设置滚转轴 (-1.0=左压杆, 1.0=右压杆)"""
    _set_axis(pyvjoy.HID_USAGE_X, value)
    if value == 0:
        print("[滚转控制反馈] 滚转回中")
    else:
        print(f"[滚转控制反馈] 滚转 {'右' if value > 0 else '左'} {abs(value) * 100:.0f}%")


def set_throttle(value):
    """设置油门 (0.0=最小, 1.0=最大)    可以用来直接设置油门数值  """
    global current_throttle
    current_throttle = max(0.0, min(1.0, value))  # 限制在0-1范围 将输入的 value 限制在 [0.0, 1.0] 范围内。如果 value < 0.0，强制设为 0.0。如果 value > 1.0，强制设为 1.0。
    _set_axis(pyvjoy.HID_USAGE_Z, 1.0- current_throttle)
    print(f"[油门控制反馈] 油门当前设置为： {current_throttle * 100:.0f}%")

def set_rudder(value):
    """设置脚舵 (-1.0=左满舵, 1.0=右满舵)"""
    _set_axis(pyvjoy.HID_USAGE_RZ, value)
    if value == 0:
        print("[舵面控制反馈] 方向舵回中")
    else:
        print(f"[舵面控制反馈] 方向舵 {'右' if value > 0 else '左'} {abs(value) * 100:.0f}%")


# --------------------------
# 复合控制指令（带中文输出）
# --------------------------

def pitch_up(duration):
    # 控制机头向上动作（拉杆）  原来-1.0=拉杆/机头向上, 1.0=推杆/机头向下，但此与DCS实际相反  可以根据需要增加2个参数 """
    global _last_rudder_value
    # 保存当前脚舵值
    _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)
    """
    将vJoy设备的原始RZ轴数值标准化到[-1.0, 1.0]范围的标准做法
    标准值 = (原始值 - 中立值) / (最大值 - 中立值)
    raw_value = 中立值 + 标准值 * (最大值 - 中立值)
    """
    set_pitch(0.1)
    # 恢复脚舵值
    set_rudder(_last_rudder_value)
    time.sleep(duration)
    set_pitch(0.0)
    print("[动作] 短时拉杆 - 机头向上，幅度：0.1，持续时间：", duration)


def pitch_up2(value=0.1, duration=None):  # 2个参数版本
    """控制机头向上动作（拉杆）   """
    """
    Args:
        value (float): 俯仰幅度 (-1.0~1.0)，负值=拉杆/机头向上，正值=推杆/机头向下
        duration (float/None): 
            - None: 保持设定值不自动回中
            - >0: 持续指定时间后自动回中（秒）
    """
    if not -1.0 <= value <= 1.0:
        raise ValueError("俯仰值必须在 -1.0 到 1.0 之间")

    global _last_rudder_value
    # 保存当前脚舵值
    _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)

    set_pitch(abs(value))  # 确保是拉杆动作
    # 恢复脚舵值
    set_rudder(_last_rudder_value)
    if duration is not None:
        time.sleep(duration)
        set_pitch(0.0)  # 自动回中
        logging.info(f"[俯仰] 拉杆 {abs(value) * 100:.0f}% 持续 {duration:.1f}s 后回中")
    else:
        logging.info(f"[俯仰] 持续拉杆 {abs(value) * 100:.0f}%")


def pitch_down(duration=0.1):
    """机头向下  原来-1.0=拉杆/机头向上, 1.0=推杆/机头向下，但此与DCS实际相反"""
    global _last_rudder_value
    # 保存当前脚舵值
    _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)
    set_pitch(-0.1)
    # 恢复脚舵值
    set_rudder(_last_rudder_value)
    time.sleep(duration)
    set_pitch(0.0)
    print("[动作] 短时推杆 - 机头向下，幅度：0.1，持续时间：", duration)


def pitch_down2(value=0.5, duration=None):
    """机头向下 双参数版本"""
    global _last_rudder_value
    # 保存当前脚舵值
    _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)
    set_pitch(-abs(value))
    # 恢复脚舵值
    set_rudder(_last_rudder_value)
    if duration is not None:
        time.sleep(duration)
        set_pitch(0.0)
    print(f"[动作] {'短时' if duration else '持续'}推杆 - 幅度:{value:.1f}" +
          (f", 持续时间:{duration:.1f}s" if duration else ""))


def roll_left(duration=0.1):
    """向左滚转"""
    global _last_rudder_value
    # 保存当前脚舵值
    _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)
    set_roll(-0.5)
    # 恢复脚舵值
    set_rudder(_last_rudder_value)
    time.sleep(duration)
    set_roll(0.0)
    print("[动作] 短时左压杆 - 向左滚转，幅度0.5，持续时间：", duration)


def roll_left2(value=0.5, duration=None):
     # 向左滚转 双参数版本
     global _last_rudder_value
     # 保存当前脚舵值
     _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)
     set_roll(-abs(value))
     # 恢复脚舵值
     set_rudder(_last_rudder_value)
     if duration is not None:
        time.sleep(duration)
        set_roll(0.0)
     print(f"[动作] {'短时' if duration else '持续'}左滚 - 幅度:{value:.1f}" +
          (f", 持续时间:{duration:.1f}s" if duration else ""))


def roll_right(duration=0.1):
    """向右滚转"""
    global _last_rudder_value
    # 保存当前脚舵值
    _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)
    set_roll(0.5)
    # 恢复脚舵值
    set_rudder(_last_rudder_value)
    time.sleep(duration)
    set_roll(0.0)
    print("[动作] 短时右压杆 - 向右滚转，幅度0.5，持续时间：", duration)


def roll_right2(value=0.5, duration=None):
    """向右滚转双参数版本"""
    global _last_rudder_value
    # 保存当前脚舵值
    _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)
    set_roll(abs(value))
    # 恢复脚舵值
    set_rudder(_last_rudder_value)
    if duration is not None:
        time.sleep(duration)
        set_roll(0.0)
    print(f"[动作] {'短时' if duration else '持续'}右滚 - 幅度:{value:.1f}" +
          (f", 持续时间:{duration:.1f}s" if duration else ""))


def throttle_up(value):
    """增加油门   (0.0=最小, 1.0=最大) value表示增加的值"""
    global _last_rudder_value
    # 保存当前脚舵值
    _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)
    global current_throttle
    new_throttle = min(1.0, current_throttle + value)
    print("[引擎] 油门增加:", value)
    set_throttle(new_throttle)
    # 恢复脚舵值
    set_rudder(_last_rudder_value)



def throttle_down(value):
    """减少油门   (0.0=最小, 1.0=最大)    value表示减少的值"""
    global current_throttle
    global _last_rudder_value
    # 保存当前脚舵值
    _last_rudder_value = (_axis_states['rz'] - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)
    new_throttle = max(0.0, current_throttle -value)
    print("[引擎] 油门减少:", value)
    set_throttle(new_throttle)
    # 恢复脚舵值
    set_rudder(_last_rudder_value)


def flaps_control():
    """襟翼收放（起飞/巡航位置）"""
    _press_button(BUTTON_FLAPS)
    print("[襟翼]切换 襟翼打开/关闭")


def flapsland_control():
    """襟翼全放（着陆位置）"""
    _press_button(BUTTON_FLAPS_LAND)
    print("[襟翼] 展开至着陆位置")


def airbrake_on_control():
    """扰流板/减速板控制"""
    _press_button(BUTTON_AIRBRAKE_EXTEND)
    print("[气动] 扰流板/减速板 打开")

def airbrake_off_control():
    """扰流板/减速板控制"""
    _press_button(BUTTON_AIRBRAKE_RETRACT)
    print("[气动] 扰流板/减速板 关闭")

def gearsoff_control():
    """收起起落架"""
    _press_button(BUTTON_GEAR_UP)
    print("[起落架] 收起")


def gearson_control():
    """放下起落架"""
    _press_button(BUTTON_GEAR_DOWN)
    print("[起落架] 放下")


def wheel_control():
    """机轮刹车"""
    _press_button(BUTTON_WHEEL_BRAKE)
    print("[刹车] 机轮刹车启动")


def foot_left(duration=0.1):
    """脚舵左转"""
    set_rudder(-1.0)
    time.sleep(duration)
    set_rudder(0.0)
    print("[舵面] 脚舵左转：1.0，持续时间", duration)


def foot_left2(value=1.0, duration=None):
    """脚舵左转双参数版本"""
    set_rudder(-abs(value))
    if duration is not None:
        time.sleep(duration)
        set_rudder(0.0)
    print(f"[舵面] {'短时' if duration else '持续'}左转 - 幅度:{value:.1f}" +
          (f", 持续时间:{duration:.1f}s" if duration else ""))


def foot_right(duration=0.1):
    """脚舵右转"""
    set_rudder(1.0)
    time.sleep(duration)
    set_rudder(0.0)
    print("[舵面] 脚舵右转：1.0，持续时间", duration)


def foot_right2(value=1.0, duration=None):
    """脚舵右转双参数版本"""
    set_rudder(abs(value))
    if duration is not None:
        time.sleep(duration)
        set_rudder(0.0)
    print(f"[舵面] {'短时' if duration else '持续'}右转 - 幅度:{value:.1f}" +
          (f", 持续时间:{duration:.1f}s" if duration else ""))


def revive_control():
    """飞机复活/重置"""
    _press_button(BUTTON_REVIVE)
    print("[系统] 飞机重置/复活")


def heading_control(target_heading, current_heading):
    """自动航向调整"""
    error = target_heading - current_heading
    if error > 180:
        error -= 360
    elif error < -180:
        error += 360

    if abs(error) > 5:
        if error > 0:
            roll_right()
            print(f"[导航] 向右调整航向 {error:.1f}°")
        else:
            roll_left()
            print(f"[导航] 向左调整航向 {abs(error):.1f}°")


def reset():
    """重置所有控制输入"""
    _vjoy_device.reset()
    global current_throttle
    current_throttle = 0.5
    print("[系统] 所有控制重置")

def get_current_controls():
    """返回当前摇杆和油门的标准化值"""
    return {
        'current_throttle': _vjoy_device.get_axis(pyvjoy.HID_USAGE_Z) / AXIS_MAX,  # [0,1]
        'current_pitch': (_vjoy_device.get_axis(pyvjoy.HID_USAGE_Y) - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL),  # [-1,1]
        'current_roll': (_vjoy_device.get_axis(pyvjoy.HID_USAGE_X) - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL),    # [-1,1]
        'current_rudder': (_vjoy_device.get_axis(pyvjoy.HID_USAGE_RZ) - AXIS_NEUTRAL) / (AXIS_MAX - AXIS_NEUTRAL)  # [-1,1]
    }
# --------------------------
# 自测试函数
# --------------------------

def _test():
    print("\n=== vJoy控制器全功能测试 ===")
    try:

        time.sleep(3)
        # # 测试基础控制
        pitch_up(1)
        time.sleep(3)

        pitch_down(1)
        time.sleep(3)
        roll_left(1)
        time.sleep(3)
        #
        roll_right(1)
        time.sleep(3)
        throttle_up(0.5)
        time.sleep(3)
        throttle_down(0.5)
        time.sleep(3)
        set_rudder(0.5)
        time.sleep(3)
        set_rudder(0)
        time.sleep(3)

        # 测试基础控制
        print("\n--- 测试双参数版本 ---")
        pitch_up2(0.3, 0.5)  # 推杆30%，持续0.5秒
        pitch_down2(0.4, 0.5)  # 拉杆40%，持续0.5秒
        roll_left2(0.3, 0.5)  # 左滚30%，持续0.5秒
        roll_right2(0.3, 0.5)  # 右滚30%，持续0.5秒
        foot_left(0.8, 0.5)  # 左舵80%，持续0.5秒
        foot_right(0.8, 0.5)  # 右舵80%，持续0.5秒


        # 测试复合功能
        flaps_control()
        time.sleep(3)
        gearson_control()
        time.sleep(3)
        gearsoff_control()
        time.sleep(3)
        airbrake_on_control()
        time.sleep(3)
        airbrake_off_control()
        time.sleep(3)
        foot_left()
        time.sleep(3)
        revive_control()
        time.sleep(3)

        print("=== 所有测试执行完成 ===")
    except Exception as e:
        print(f"!!! 测试失败: {e}")
    finally:
        reset()


if __name__ == "__main__":
    _test()